package org.ns.framework.datasource.mybatis;

import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.sql.DataSource;

import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.ns.framework.datasource.anns.DBSource;
import org.ns.framework.datasource.config.DataSourceConfig;
import org.ns.framework.datasource.config.MybatisConfiguration;
import org.ns.framework.datasource.config.MybatisConfiguration.MybatisParam;
import org.ns.framework.datasource.config.MybatisConfigurationManage;
import org.ns.framework.datasource.config.MybatisSqlSessionFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;

public class MybatisMapperManage implements ApplicationContextAware {

	private Map<String, SqlSessionFactory> sqlSessionFactoryMap = new ConcurrentHashMap<>();
	private ApplicationContext applicationContext;
	@Autowired
	MybatisConfigurationManage mybatisConfigurationManage;
	@Autowired(required = false)
	MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean;
	@Autowired(required = false)
	DataSourceConfig dataSourceConfig;

	Map<String, Boolean >  mapperInitStatus = new ConcurrentHashMap<>();
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
	
	public void setMybatisConfigurationManage(MybatisConfigurationManage mybatisConfigurationManage) {
		this.mybatisConfigurationManage = mybatisConfigurationManage;
	}
	
	public void setMybatisSqlSessionFactoryBean(MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean) {
		this.mybatisSqlSessionFactoryBean = mybatisSqlSessionFactoryBean;
	}
	
	public boolean isAutoCommit(String dataSourceName) {
		if (dataSourceConfig == null) {
			return false;
		}
		return dataSourceConfig.autoCommit(dataSourceName);
	}
	/**
	 * 设置SqlSessionFactory, 如果存在则无法设置.
	 * @param dataSourceName
	 * @param sqlSessionFactory
	 * @return 如果设置成功返回true, 如果存在则无法设置返回false
	 */
	public boolean setSqlSessionFactory(String dataSourceName, SqlSessionFactory sqlSessionFactory, boolean initConfig) {
		if (sqlSessionFactoryMap.putIfAbsent(dataSourceName, sqlSessionFactory) == null) {
			if (initConfig && mybatisConfigurationManage != null ) {
				List<MybatisConfiguration> mybatisConfigurations = mybatisConfigurationManage
						.getMybatisConfigurations(dataSourceName);
				MybatisParam mybatisParam = new MybatisParam(dataSourceName, sqlSessionFactory.getConfiguration(),
						sqlSessionFactory);
				for (MybatisConfiguration mybatisConfiguration : mybatisConfigurations) {
					mybatisConfiguration.configuration(mybatisParam);
				}
			}
			return true;
		}
		return false;
	}

	public SqlSessionFactory getSqlSessionFactory(String dataSourceName) throws BeansException {
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryMap.get(dataSourceName);
		if (sqlSessionFactory == null) {
			if (!applicationContext.containsBean(dataSourceName)) { // 不存在数据源.
				return null;
			}
			Object contextBean = applicationContext.getBean(dataSourceName);
			if (contextBean instanceof SqlSessionFactory) {
				sqlSessionFactory = (SqlSessionFactory) contextBean;
			} else if (contextBean instanceof DataSource) {
				DataSource dataSource = (DataSource) contextBean;
				if (mybatisSqlSessionFactoryBean != null) {
					try {
						sqlSessionFactory = mybatisSqlSessionFactoryBean.build(dataSourceName, dataSource);
					} catch (Exception e) {
						throw new BeanCreationException("sqlSessionFactory type create fail:" + dataSourceName, e);
					}
				} else {
					// 构建 SqlSessionFactory
					Environment environment = new Environment(dataSourceName, new JdbcTransactionFactory(), dataSource);
					Configuration configuration = new Configuration(environment);

					SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
					sqlSessionFactory = factoryBuilder.build(configuration);
				}
			} else {
				throw new BeanCreationException("sqlSessionFactory type create fail:" + dataSourceName);
			}
			sqlSessionFactoryMap.put(dataSourceName, sqlSessionFactory);
			List<MybatisConfiguration> mybatisConfigurations = mybatisConfigurationManage
					.getMybatisConfigurations(dataSourceName);
			MybatisParam mybatisParam = new MybatisParam(dataSourceName, sqlSessionFactory.getConfiguration(),
					sqlSessionFactory);
			for (MybatisConfiguration mybatisConfiguration : mybatisConfigurations) {
				mybatisConfiguration.configuration(mybatisParam);
			}
		}

		return sqlSessionFactory;
	}

	public Object getMapper(String datasourceName, Class<?> fieldType) {
		boolean defaultSourceName = false;
		if (datasourceName.startsWith("##")) { // 如果数据源名字是 ##开头
			defaultSourceName = true;			// 这个数据源作为未指定的默认源.
        	datasourceName = datasourceName.substring(2).trim(); // 获得源名称
        }
		
		if (defaultSourceName) { // 如果是默认数据源, 则从接口类型中查找制定源
			DBSource dbSource = AnnotatedElementUtils.findMergedAnnotation(fieldType, DBSource.class);
			if (dbSource != null) { // 如果接口有强制指定的源. 则直接使用当前源名称, 这个名称不可以是##开头
				if (!dbSource.value().isEmpty() && !dbSource.value().startsWith("##")) {
					datasourceName = dbSource.value();
				}
			}
		}
		
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(datasourceName);
		if (sqlSessionFactory == null) {
			return null;
		}
		Object value = null;
		if (fieldType == SqlSessionFactory.class) {
			value = sqlSessionFactory;
		} else if (fieldType == DataSource.class) {
			value = sqlSessionFactory.getConfiguration().getEnvironment().getDataSource();
		} else if (fieldType.isInterface()) {
			String lockName = datasourceName + "#" + fieldType.getName();
			if (!mapperInitStatus.containsKey(lockName)) {
				Configuration configuration = sqlSessionFactory.getConfiguration();
				synchronized (configuration) {
					if (!mapperInitStatus.containsKey(lockName)) {
						if (!configuration.hasMapper(fieldType)) {
							configuration.addMapper(fieldType);
						}
						mapperInitStatus.put(lockName, true);
					}
				}
			}

			MapperProxy mapperProxy = new MapperProxy(sqlSessionFactory, datasourceName, this, fieldType);
			value = Proxy.newProxyInstance(fieldType.getClassLoader(), new Class[] { fieldType }, mapperProxy);
		}
		return value;
	}
}
